#include <iostream>
#include "cscan.h"
#include "props.h"
#include "convert.h"

namespace eintik::data::scan
{
    CScan::CScan()
    {
        file_cache()->register_scan(reinterpret_cast<uint64_t>(this));
    }

    void CScan::add_frame(const std::vector<int16_t> &frame, int64_t tick)
    {
        if (-1 == tick_head) {
            tick_head = tick;
        }
        tick -= tick_head;
        // CAUTION: us to ms
        tick /= 1000;

        // check parameters
        auto cnt_point = eintik::property::cnt_points();
        if (cnt_point <= 0)
        {
            return;
        }
        #if 0
        *** 0917
        auto id_gate_start = eintik::property::pos_gate_start(id_gate);
        if (!(id_gate_start >= 0 && id_gate_start < cnt_point))
        {
            return;
        }
        auto id_gate_end = eintik::property::pos_gate_end(id_gate);
        if (!(id_gate_end >= 0 && id_gate_end < cnt_point))
        {
            return;
        }
        if (id_gate_start > id_gate_end)
        {
            return;
        }
        // std::cout << "id_gate: " << id_gate << ", id_gate_start: " << id_gate_start << ", id_gate_end: " << id_gate_end << std::endl;
        #endif
        auto interval = eintik::property::cscan_interval();
        if (interval <= 0)
        {
            return;
        }
        auto res_enc = static_cast<double>(eintik::property::encoder_resolution());
        if (res_enc <= 0)
        {
            return;
        }
        #if 0
        *** 0917
        auto h = frame.size() / cnt_point;
        if (dq.size() > 0)
        {
            if (h != dq.back().c.size())
            {
                // CAUTION: height of cscan is changed, no action should be taken anymore.
                return;
            }
        }
        #endif
        auto h = property::cnt_beam();

        // remove data when backward,
        while (!dq.empty())
        {
            if (dq.back().t >= tick)
            {
                if (dq_prev.empty()) {
                    auto pos = dq.front().t / res_enc;
                    // CAUTION: pos set to the middle of interval, easy to check
                    pos -= interval / 2;
                    file_cache()->load_data(reinterpret_cast<uint64_t>(this), pos, dq_prev, h);
                }
                dq.pop_back();
                if (!dq_prev.empty()) {
                    dq.push_front(std::move(dq_prev.back()));
                    dq_prev.pop_back();
                }
            } else {
                break;
            }
        }

        auto &&full = [&](std::deque<PosFrame> &q) {
            return q.size() > 0 && (q.back().t - q.front().t) / res_enc >= interval;
        };

        auto tb = dq.size() > 0 ? dq.back().t : 0;
        if (tick - tb > res_enc)
        {
            while (full(dq))
            {
                if (full(dq_prev))
                {
                    file_cache()->save_data(
                        reinterpret_cast<uint64_t>(this), 
                        dq_prev, 
                        dq_prev.front().t / res_enc, 
                        dq_prev.back().t / res_enc);
                    dq_prev.clear();
                }
                dq_prev.push_back(std::move(dq.front()));
                dq.pop_front();
            }

            PosFrame pf;
            pf.t = tick;
            pf.c.reserve(h);
            for (auto i = 0; i < h; i++)
            {
                auto a = frame.data() + i * cnt_point;
                int id_gate_start, id_gate_end;
                if (eintik::property::pos_gate_isync(id_gate, a, cnt_point, id_gate_start, id_gate_end))
                {
                    auto b = *std::max_element(
                        a + id_gate_start,
                        a + id_gate_end,
                        [](int16_t a, int16_t b)
                        { return std::abs(a) < std::abs(b); });
                    auto c = int16_2_uint8(b);
                    pf.c.push_back(c);
                } else {
                    pf.c.push_back(0);
                }
            }
            dq.push_back(std::move(pf));
        }
    }

    std::vector<uint8_t> CScan::frame_c(int w, int pad4)
    {
        // check parameters
        if (dq.empty()) {
            return {};
        }
        auto h = dq.front().c.size();
        if (w <= 0 || h <= 0) {
            return {};
        }
        auto interval = eintik::property::cscan_interval();
        if (interval <= 0) {
            return {};
        }
        auto res_enc = static_cast<double>(eintik::property::encoder_resolution());
        if (res_enc <= 0) {
            return {};
        }

        // create slice
        auto &&pos = [&](int id)
        {
            return (dq.at(id).t - dq.front().t) / res_enc;
        };

        auto &&to_right = [&](int id_start, double x)
        {
            for (auto i = id_start; i < dq.size(); i++)
            {
                if (pos(i) >= x)
                {
                    return i;
                }
            }
            return -1;
        };

        auto &&to_left = [&](int id_start, double x)
        {
            for (auto i = id_start; i >= 0; i--)
            {
                if (pos(i) <= x)
                {
                    return i;
                }
            }
            return -1;
        };

        auto id_prev = 0;

        auto &&find = [&](double x)
        {
            if (dq.size() == 0)
            {
                return -1;
            }
            auto id = 0;
            if (pos(id_prev) < x)
            {
                id = to_right(id_prev, x);
            }
            else
            {
                id = to_left(id_prev, x);
            }
            return id;
        };

        std::vector<uint8_t> ret;
        ret.reserve((w + pad4) * h);

        for (auto i = 0; i < h; i++)
        {
            id_prev = 0;
            for (auto j = 0; j < w; j++)
            {
                auto x = j * interval / w;
                auto id = find(x);
                if (-1 == id)
                {
                    ret.push_back(0);
                }
                else
                {
                    ret.push_back(dq.at(id).c.at(i));
                    id_prev = id;
                }
            }
            for (auto j = 0; j < pad4; j++)
            {
                ret.push_back(0);
            }
        }
        return ret;
    }

    std::pair<double, double> CScan::range()
    {
        if (dq.size() == 0)
        {
            return {0, 0};
        }
        auto res_enc = eintik::property::encoder_resolution();
        if (res_enc <= 0) {
            return {0, 0};
        }
        auto interval = eintik::property::cscan_interval();
        if (interval <= 0) {
            return {0, 0};
        }
        auto a = dq.front().t / static_cast<double>(res_enc);
        auto b = dq.back().t / static_cast<double>(res_enc);
        return {a, b};
    }
}
